// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/renderer/pepper/url_request_info_util.h"

#include <stddef.h>
#include <stdint.h>

#include "base/logging.h"
#include "base/strings/string_util.h"
#include "content/child/request_extra_data.h"
#include "content/common/fileapi/file_system_messages.h"
#include "content/renderer/pepper/host_globals.h"
#include "content/renderer/pepper/pepper_file_ref_renderer_host.h"
#include "content/renderer/pepper/pepper_plugin_instance_impl.h"
#include "content/renderer/pepper/plugin_module.h"
#include "content/renderer/pepper/renderer_ppapi_host_impl.h"
#include "content/renderer/render_thread_impl.h"
#include "net/http/http_util.h"
#include "ppapi/c/pp_bool.h"
#include "ppapi/c/pp_var.h"
#include "ppapi/proxy/ppapi_messages.h"
#include "ppapi/shared_impl/url_request_info_data.h"
#include "ppapi/shared_impl/var.h"
#include "ppapi/thunk/enter.h"
#include "third_party/WebKit/public/platform/FilePathConversion.h"
#include "third_party/WebKit/public/platform/WebData.h"
#include "third_party/WebKit/public/platform/WebHTTPBody.h"
#include "third_party/WebKit/public/platform/WebURL.h"
#include "third_party/WebKit/public/platform/WebURLRequest.h"
#include "third_party/WebKit/public/web/WebDocument.h"
#include "third_party/WebKit/public/web/WebFrame.h"
#include "url/gurl.h"
#include "url/url_util.h"

using blink::WebData;
using blink::WebFrame;
using blink::WebHTTPBody;
using blink::WebString;
using blink::WebURL;
using blink::WebURLRequest;
using ppapi::Resource;
using ppapi::URLRequestInfoData;
using ppapi::thunk::EnterResourceNoLock;

namespace content {

namespace {

    // Appends the file ref given the Resource pointer associated with it to the
    // given HTTP body, returning true on success.
    bool AppendFileRefToBody(PP_Instance instance,
        PP_Resource resource,
        int64_t start_offset,
        int64_t number_of_bytes,
        PP_Time expected_last_modified_time,
        WebHTTPBody* http_body)
    {
        base::FilePath platform_path;
        PepperPluginInstanceImpl* instance_impl = HostGlobals::Get()->GetInstance(instance);
        if (!instance_impl)
            return false;

        RendererPpapiHost* renderer_ppapi_host = instance_impl->module()->renderer_ppapi_host();
        if (!renderer_ppapi_host)
            return false;
        ppapi::host::ResourceHost* resource_host = renderer_ppapi_host->GetPpapiHost()->GetResourceHost(resource);
        if (!resource_host || !resource_host->IsFileRefHost())
            return false;
        PepperFileRefRendererHost* file_ref_host = static_cast<PepperFileRefRendererHost*>(resource_host);
        switch (file_ref_host->GetFileSystemType()) {
        case PP_FILESYSTEMTYPE_LOCALTEMPORARY:
        case PP_FILESYSTEMTYPE_LOCALPERSISTENT:
            // TODO(kinuko): remove this sync IPC when we fully support
            // AppendURLRange for FileSystem URL.
            RenderThreadImpl::current()->Send(
                new FileSystemHostMsg_SyncGetPlatformPath(
                    file_ref_host->GetFileSystemURL(), &platform_path));
            break;
        case PP_FILESYSTEMTYPE_EXTERNAL:
            platform_path = file_ref_host->GetExternalFilePath();
            break;
        default:
            NOTREACHED();
        }
        http_body->appendFileRange(blink::FilePathToWebString(platform_path),
            start_offset, number_of_bytes,
            expected_last_modified_time);
        return true;
    }

    // Checks that the request data is valid. Returns false on failure. Note that
    // method and header validation is done by the URL loader when the request is
    // opened, and any access errors are returned asynchronously.
    bool ValidateURLRequestData(const URLRequestInfoData& data)
    {
        if (data.prefetch_buffer_lower_threshold < 0 || data.prefetch_buffer_upper_threshold < 0 || data.prefetch_buffer_upper_threshold <= data.prefetch_buffer_lower_threshold) {
            return false;
        }
        return true;
    }

    std::string FilterStringForXRequestedWithValue(const std::string& s)
    {
        std::string rv;
        rv.reserve(s.length());
        for (size_t i = 0; i < s.length(); i++) {
            char c = s[i];
            // Allow ASCII digits, letters, periods, commas, and underscores. (Ignore
            // all other characters.)
            if (base::IsAsciiDigit(c) || base::IsAsciiAlpha(c) || (c == '.') || (c == ',') || (c == '_'))
                rv.push_back(c);
        }
        return rv;
    }

    // Returns an appropriate value for the X-Requested-With header for plugins that
    // present an X-Requested-With header. Returns a blank string for other plugins.
    // We produce a user-agent-like string (eating spaces and other undesired
    // characters) like "ShockwaveFlash/11.5.31.135" from the plugin name and
    // version.
    std::string MakeXRequestedWithValue(const std::string& name,
        const std::string& version)
    {
        std::string rv = FilterStringForXRequestedWithValue(name);
        if (rv.empty())
            return std::string();

        // Apply to a narrow list of plugins only.
        if (rv != "ShockwaveFlash" && rv != "PPAPITests")
            return std::string();

        std::string filtered_version = FilterStringForXRequestedWithValue(version);
        if (!filtered_version.empty())
            rv += "/" + filtered_version;

        return rv;
    }

} // namespace

bool CreateWebURLRequest(PP_Instance instance,
    URLRequestInfoData* data,
    WebFrame* frame,
    WebURLRequest* dest)
{
    // In the out-of-process case, we've received the URLRequestInfoData
    // from the untrusted plugin and done no validation on it. We need to be
    // sure it's not being malicious by checking everything for consistency.
    if (!ValidateURLRequestData(*data))
        return false;

    std::string name_version;

    // Allow instance to be 0 or -1 for testing purposes.
    if (instance && instance != -1) {
        PepperPluginInstanceImpl* instance_impl = HostGlobals::Get()->GetInstance(instance);
        if (instance_impl) {
            name_version = MakeXRequestedWithValue(
                instance_impl->module()->name(),
                instance_impl->module()->version());
        }
    } else {
        name_version = "internal_testing_only";
    }

    dest->setURL(frame->document().completeURL(WebString::fromUTF8(data->url)));
    dest->setDownloadToFile(data->stream_to_file);
    dest->setReportUploadProgress(data->record_upload_progress);

    if (!data->method.empty())
        dest->setHTTPMethod(WebString::fromUTF8(data->method));

    dest->setFirstPartyForCookies(frame->document().firstPartyForCookies());

    const std::string& headers = data->headers;
    if (!headers.empty()) {
        net::HttpUtil::HeadersIterator it(headers.begin(), headers.end(), "\n\r");
        while (it.GetNext()) {
            dest->addHTTPHeaderField(WebString::fromUTF8(it.name()),
                WebString::fromUTF8(it.values()));
        }
    }

    // Append the upload data.
    if (!data->body.empty()) {
        WebHTTPBody http_body;
        http_body.initialize();
        int file_index = 0;
        for (size_t i = 0; i < data->body.size(); ++i) {
            const URLRequestInfoData::BodyItem& item = data->body[i];
            if (item.is_file) {
                if (!AppendFileRefToBody(instance,
                        item.file_ref_pp_resource,
                        item.start_offset,
                        item.number_of_bytes,
                        item.expected_last_modified_time,
                        &http_body))
                    return false;
                file_index++;
            } else {
                DCHECK(!item.data.empty());
                http_body.appendData(WebData(item.data));
            }
        }
        dest->setHTTPBody(http_body);
    }

    // Add the "Referer" header if there is a custom referrer. Such requests
    // require universal access. For all other requests, "Referer" will be set
    // after header security checks are done in AssociatedURLLoader.
    if (data->has_custom_referrer_url && !data->custom_referrer_url.empty())
        frame->setReferrerForRequest(*dest, GURL(data->custom_referrer_url));

    if (data->has_custom_content_transfer_encoding && !data->custom_content_transfer_encoding.empty()) {
        dest->addHTTPHeaderField(
            WebString::fromUTF8("Content-Transfer-Encoding"),
            WebString::fromUTF8(data->custom_content_transfer_encoding));
    }

    if (data->has_custom_user_agent || !name_version.empty()) {
        RequestExtraData* extra_data = new RequestExtraData();
        if (data->has_custom_user_agent) {
            extra_data->set_custom_user_agent(
                WebString::fromUTF8(data->custom_user_agent));
        }
        if (!name_version.empty()) {
            extra_data->set_requested_with(WebString::fromUTF8(name_version));
        }
        dest->setExtraData(extra_data);
    }

    return true;
}

bool URLRequestRequiresUniversalAccess(const URLRequestInfoData& data)
{
    return data.has_custom_referrer_url || data.has_custom_content_transfer_encoding || data.has_custom_user_agent || url::FindAndCompareScheme(data.url, url::kJavaScriptScheme, NULL);
}

} // namespace content
